package com.atguigu.spzx.common.log.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.spzx.common.handler.exception.SpzxException;
import com.atguigu.spzx.common.log.annnotations.SpzxLog;
import com.atguigu.spzx.common.log.service.SysOperLogService;
import com.atguigu.spzx.common.util.utils.ThreadLocalUtil;
import com.atguigu.spzx.model.entity.sys.SysOperLog;
import com.atguigu.spzx.model.entity.sys.SysUser;
import com.atguigu.spzx.model.result.ResultCodeEnum;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;

@Aspect
@Order(value = -1) //值越小 优先级越高
// 多个切面类一起使用时，会存在以下问题
//   Transactional  SpzxLogAspect   BrandController : 如果事务切面优先级高于我们自定义的切面，请求先交给事务，然后再交给我们的切面
//   如果我们的切面通知方法执行时 将目标方法的异常捕获 处理，没有抛出，会导致 事务切面无法获取异常，无法回滚 直接提交
public class SpzxLogAspect {
    @Resource
    SysOperLogService sysOperLogService;
    //日志切面通知方法：对接口方法进行动态代码 每个接口方法都有返回结果
    //因为保存日志需要获取目标方法执行前后的数据 所以使用环绕通知
//    @Around(value = "execution(* *.*.*(..))") //value:execution  要写切面表达式
    //* *.*.*(..) :表示返回值任意类型，所有的包下的所有类的任意方法 方法任意形参
    @Around(value = "@annotation(spzxLog)") //@annotation() 可以指定使用注解作为切入点
    public Object arround(ProceedingJoinPoint joinPoint, SpzxLog spzxLog){//ProceedingJoinPoint表示连接点的对象(可以获取目标方法的参数 方法对象)
        //保存操作日志
        SysOperLog log = new SysOperLog();

        try {
            //1、获取请求访问目标方法的参数列表
            Object[] args = joinPoint.getArgs();
            //==============设置目标方法执行前的操作日志参数
            // ！！因为初始化日志对象需要的一些属性值动态代码中无法获取，可以使用注解在标注时初始化，通知方法中获取使用
            //  title: 操作的模块名称，operatorType: 操作方式(后台用户 MANAGE、手机用户 mobile) ，
            //  businessType(1,2,3,4), isRequestParamSave: 是否保存请求参数，isResponseSave: 是否保存响应数据
            //获取访问的目标方法对象
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            //获取请求报文对象： springboot项目通过过滤器，在请求进来时会将当前请求对象存到RequestContextHolder中
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = requestAttributes.getRequest();

            // 获取日志注解对象：获取注解中的属性值
            log.setTitle(spzxLog.title());
            String name = method.getDeclaringClass().getName();
            log.setMethod(name+"."+method.getName());
            log.setRequestMethod(request.getMethod());
            log.setOperatorType(spzxLog.operatorType().getType());
            log.setBusinessType(spzxLog.businessType());
            //获取请求url地址
            String requestURI = request.getRequestURI();
            log.setOperUrl(requestURI);
            //获取请求用户的客户端ip地址
            String remoteHost = request.getRemoteHost();
            log.setOperIp(remoteHost);
            //判断知否需要保存请求参数
            if(spzxLog.isRequestParamSave()){
                log.setOperParam(JSON.toJSONString(args));
            }
            //通过登录认证拦截器获取操作管理员的信息
            SysUser sysUser = ThreadLocalUtil.get();
            log.setOperName(sysUser.getName());
            //2、执行目标方法得到目标方法的返回值
            Object proceed = joinPoint.proceed(args);
            //设置正常执行的日志
            if(spzxLog.isResponseSave()){
                log.setJsonResult(JSON.toJSONString(proceed));
            }
            log.setStatus(0);
            log.setErrorMsg(null);
            //3、返回目标方法的执行结果
            return proceed;
        } catch (Throwable e) {
            log.setJsonResult(null);
            log.setStatus(1);
            log.setErrorMsg(e.getMessage());
            //在自定义切面中出现异常时一般直接抛出
            throw new SpzxException(ResultCodeEnum.FAIL,e);
        }finally {
            //无论是否有异常都需要保存操作日志
            //非核心业务： 如果阻塞执行 会导致接口响应速度变慢
            // 100个线程  ： QueryPerSecond =  100*1*1000/ 核心业务执行时长 200ms+非核心业务执行时长 100ms
//            sysOperLogService.save(log);
            sysOperLogService.asyncSave(log);
        }
    }
}
